package ch.fhnw.ams.sudoku.gui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

import ch.fhnw.ams.sudoku.SudokuField;
import ch.fhnw.ams.sudoku.SudokuGrid;

/**
 * Represents the graphic of {@link SudokuGrid}. This grid consists of 81 field components.
 * 
 * @author amsler
 *
 */
public class GridPanel extends JPanel implements KeyListener, MouseListener {

	private static final long serialVersionUID = 1L;
	public static final int DEFAULT_WIDTH = SudokuGrid.COLUMN_SIZE * FieldComponent.DEFAULT_WIDTH;
	public static final int DEFAULT_HEIGHT = SudokuGrid.ROW_SIZE * FieldComponent.DEFAULT_HEIGHT;
	public static final Color DEFAULT_BACKGROUD_COLOR = Color.WHITE;
	public static final Color DEFAULT_GRID_COLOR = Color.BLACK;
	public static final float STROKE_SIZE = 4.0f;
	
	private SudokuGrid sudokuGrid;
	private FieldComponent[][] fieldComponents = new FieldComponent[SudokuGrid.ROW_SIZE][SudokuGrid.COLUMN_SIZE];
	private FieldComponent selectedField;
	private boolean modifyPuzzleNumbers;
	
	
	/**
	 * Creates a new empty grid.
	 */
	public GridPanel() {
		setModifyPuzzleNumbers(false);
		setLayout(new GridLayout(SudokuGrid.ROW_SIZE, SudokuGrid.COLUMN_SIZE));
		addKeyListener(this);
		addMouseListener(this);
		setFocusable(true);
		setBackground(DEFAULT_BACKGROUD_COLOR);
		
		for (int row = 0; row < SudokuGrid.ROW_SIZE; row++) {
			for (int column = 0; column < SudokuGrid.COLUMN_SIZE; column++) {
				FieldComponent fieldPanel = new FieldComponent(row, column);
				fieldComponents[row][column] = fieldPanel;
				add(fieldPanel);
			}
		}
		
		//set upper left field selected
		selectedField = fieldComponents[0][0];
		selectedField.setSelected(true);
	}
	
	
	/**
	 * Creates a grid where numbers are taken from the {@link SudokuGrid}
	 * supplied.
	 * 
	 * @param sudokuGrid
	 *            the {@link SudokuGrid} used to specify the graphic grid
	 */
	public GridPanel(SudokuGrid sudokuGrid) {
		this();
		this.sudokuGrid = sudokuGrid;
		updateFieldComponents();
	}
	
	private void updateFieldComponents() {
		for (int row = 0; row < SudokuGrid.ROW_SIZE; row++) {
			for (int column = 0; column < SudokuGrid.COLUMN_SIZE; column++) {
				SudokuField sudokuField = sudokuGrid.getField(row, column);
				fieldComponents[row][column].setSudokuField(sudokuField);
			}
		}
		repaint();
	}
	
	/**
	 * paints the grid and all child elements.
	 */
	@Override 
	public void paint(Graphics g) {
		//paint background and child elements
		super.paint(g);
		
		//paint grid on top of the child elements
		Graphics2D g2d = (Graphics2D) g.create();
		
		BasicStroke stroke = new BasicStroke(STROKE_SIZE);
		g2d.setColor(DEFAULT_GRID_COLOR);
		g2d.setStroke(stroke);
		
		//draw frame
		int offset = (int) (STROKE_SIZE / 2.0f);
		int y = offset;
		int x = offset;
		int width = getWidth() - (int) STROKE_SIZE;
		int height = getHeight() - (int) STROKE_SIZE;
		g2d.drawRect(x, y, width, height);
		
		//draw grid lines
		for (int row = 0; row < fieldComponents.length; row++) {
			if (row % 3 == 0 && row != 0) {
				FieldComponent component = fieldComponents[row][0];
				int lineY = component.getLocation().y;
				g2d.drawLine(0, lineY, getWidth(), lineY);
			}
		}
		for (int column = 0; column < fieldComponents.length; column++) {
			if (column % 3 == 0 && column != 0) {
				FieldComponent component = fieldComponents[0][column];
				int lineX = component.getLocation().x;
				g2d.drawLine(lineX, 0, lineX, getHeight());
			}
		}
		
		g2d.dispose();
	}
	
	/**
	 * This method should be invoked by EDT
	 * 
	 * @param sudokuGrid
	 *            sthe {@link SudokuGrid} used to specify the graphic grid.
	 */
	public void setSudokuGrid(SudokuGrid sudokuGrid) {
		this.sudokuGrid = sudokuGrid;
		updateFieldComponents();
	}

	/**
	 * Handles key inputs.
	 */
	@Override
	public void keyPressed(KeyEvent e) {
		int keyCode = e.getKeyCode();
		char keyChar = e.getKeyChar();
		
		//handle arrow key
		if (keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN
				|| keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT) {
			updateSelectedField(keyCode);
			return;
		}
		
		//handle numbers
		int number = Character.digit(keyChar, 10);
		if (number > 0 && number < 10) {
			SudokuField field = selectedField.getSudokuField();
			if (field.isPuzzelNumber() && modifyPuzzleNumbers || !field.isPuzzelNumber()){
				field.setNumber(number);
				repaint();
				
			}
			return;
		}
		
		//handle delete request
		if (keyCode == KeyEvent.VK_DELETE || keyCode == KeyEvent.VK_BACK_SPACE) {
			SudokuField field = selectedField.getSudokuField();
			if (field.isPuzzelNumber() && modifyPuzzleNumbers || !field.isPuzzelNumber()){
				field.delete();
				repaint();
			} 
			return;
		}
	}
	
	private void updateSelectedField(final int arrowDirection) {
		int row = selectedField.getRow();
		int column = selectedField.getColumn();
		
		switch (arrowDirection) {
		case KeyEvent.VK_UP:
			if (row > 0) {
				row--;
			}
			break;
		case KeyEvent.VK_DOWN:
			if (row < SudokuGrid.ROW_SIZE - 1) {
				row++;
			}
			break;
		case KeyEvent.VK_LEFT:
			if (column > 0) {
				column--;
			}
			break;
		case KeyEvent.VK_RIGHT:
			if (column < SudokuGrid.COLUMN_SIZE - 1) {
				column++;
			}
			break;
		default:
			return;
		}
		
		FieldComponent newSelected = fieldComponents[row][column];
		if (newSelected != selectedField) {
			selectedField.setSelected(false);
			newSelected.setSelected(true);
			selectedField = newSelected;
			repaint();
		}
	}
	
	/**
	 * Handles mouse inputs
	 */
	@Override
	public void mousePressed(MouseEvent e) {
		Point point = e.getPoint();
		Component component = getComponentAt(point);
		
		if (component instanceof FieldComponent) {
			FieldComponent field = (FieldComponent) component;
			if (field != selectedField) {
				selectedField.setSelected(false);
				field.setSelected(true);
				selectedField = field;
				repaint();
			}
		}
	}
	
	@Override
	public void mouseClicked(MouseEvent e) {}

	@Override
	public void keyReleased(KeyEvent e) {}
	
	@Override
	public void keyTyped(KeyEvent e) {}

	@Override
	public void mouseReleased(MouseEvent e) {}

	@Override
	public void mouseEntered(MouseEvent e) {}

	@Override
	public void mouseExited(MouseEvent e) {}

	
	/**
	 * Set <code>true</code> in create mode otherwise set <code>false</code>
	 * 
	 * @param modifyPuzzleNumbers
	 *            <code>true</code> if user can modify puzzle numbers
	 */
	public void setModifyPuzzleNumbers(boolean modifyPuzzleNumbers) {
		this.modifyPuzzleNumbers = modifyPuzzleNumbers;
	}
	
	/**
	 * Gets the graphic of this component. This method should be invoked by EDT.
	 * 
	 * @return the grid as image
	 */
	public BufferedImage getImage() {
		BufferedImage bufferedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
		Graphics imgGraphics = bufferedImage.getGraphics();
		
		selectedField.setSelected(false);
		paint(imgGraphics);
		selectedField.setSelected(true);
		
		return bufferedImage;
	}
}
